// AForceEnv.java
// Jim Sproch
// Created: April 29, 2006
// Modified: March 30, 2008
// Part of the Aforce Port
// Mac < Windows < Linux

/**
	AForceEnv is a big, black map that can hold objects!
	@author Jim Sproch
	@version 0.1a beta
*/


import java.util.*;
import javax.swing.*;

public class AForceEnv extends JComponent implements AbstractObject
{
	Size mysize;
	Location mylocation;
	private int scores[] = new int[9];
	private static int level = 0;
	AForce aforce;

	// Because this object is Serializable because it extends JComponent
	private static final long serialVersionUID = 7526471155622776147L;

	// Array Lists are exclusive, items are NOT repeated down the tree
	private ArrayList<AbstractObject> abstractobjects = new ArrayList<AbstractObject>();
	private ArrayList<DisplayableObject> displayableobjects = new ArrayList<DisplayableObject>();
	private ArrayList<MovableObject> movableobjects = new ArrayList<MovableObject>();

	private ArrayList<Boundary> boundariesv = new ArrayList<Boundary>();
	private ArrayList<Boundary> boundariesh = new ArrayList<Boundary>();


	public void AForceEnvhelper()
	{
		// tempblock must be a block so the ships will stop when they hit it
		// and so the bullets will die when they hit it.  Just a hack
		Block aforceenvblock = new Block(null, new Size(0,0), new Location(0,0));

		// AForce.nextlevel() will call AForceEnv.AForceEnvHelper() (This method), which needs to clear the arrays
		// For the next level, or ships and other objects from the previous level will appear in the new level.
		// This happens because some things (such as the good guy ship and powerups) are not removed.
		while(!abstractobjects.isEmpty()) abstractobjects.remove(0);
		while(!displayableobjects.isEmpty()) displayableobjects.remove(0);
		while(!movableobjects.isEmpty()) movableobjects.remove(0);
		// We even have this problem with boundaries!
		while(!boundariesv.isEmpty()) boundariesv.remove(0);
		while(!boundariesh.isEmpty()) boundariesh.remove(0);

		ArrayList<Boundary> boundaries = new ArrayList<Boundary>();
		boundaries.add(new Boundary(new Location(0,0), 0, 0, Direction.EAST, mysize.getx(), aforceenvblock));
		boundaries.add(new Boundary(new Location(0,0), 0, mysize.gety(), Direction.EAST, mysize.getx(), aforceenvblock));
		boundaries.add(new Boundary(new Location(0,0), 0, 0, Direction.SOUTH, mysize.gety(), aforceenvblock));
		boundaries.add(new Boundary(new Location(0,0), mysize.getx(), 0, Direction.SOUTH, mysize.gety(), aforceenvblock));
		insert(boundaries);
	}

	public int numobjects()
	{
		return abstractobjects.size()+displayableobjects.size()+movableobjects.size();
	}

	public int numobjects(Class<?> type)
	{
		if(type == AbstractObject.class) return abstractobjects.size();
		if(type == DisplayableObject.class) return displayableobjects.size();
		if(type == MovableObject.class) return movableobjects.size();
		Printer.err.println("ERROR 726: Unknown Object Type #AForceEnv.numobjects(int)");
		return 0;
	}

	public void destroy()
	{
	
	}

	public AForce getAForce()
	{
		return aforce;
	}

	public Status getStatus()
	{
		return null;
	}

	public AForceEnv(Size size)
	{
		mysize = size;

		resetLevel();
		resetScores();

		AForceEnvhelper();
	}

	public Size getsize()
	{
		return mysize;
	}


	public void setAForce(AForce aforce)
	{
		this.aforce = aforce;
	}

	public ArrayList<Boundary> getBoundaries()
	{
		return new ArrayList<Boundary>();
	}

	
	public Location getlocation()
	{
		return mylocation;
	}


	public void nextLevel()
	{
		level++;
	}

	public void addPoints(int team, int points)
	{
		scores[team] = scores[team] + points;
	}

	public int getScore(int team)
	{
		return scores[team];
	}

	public void resetScores()
	{
		for(int x = 0; x < scores.length; x++) setScore(x, 0);
	}

	public void setScore(int team, int score)
	{
		scores[team] = score;
	}

	public void resetLevel()
	{
		level = 0;
	}

	public static int getLevel()
	{
		return level;
	}

	public void addobject(AbstractObject item)
	{
		if(item instanceof MovableObject)
		{
			if(item != null) ((MovableObject)item).setField(this);
			if(item != null) insert(item.getBoundaries()); // insert boundaries into list
			if(item != null) movableobjects.add((MovableObject)item);
		}
		else if(item instanceof DisplayableObject)
		{
			displayableobjects.add((DisplayableObject)item);
		//	aforce.addobject((DisplayableObject)item);
			insert(item.getBoundaries());
		}
		else if(item instanceof AbstractObject)
		{
			abstractobjects.add(item);
			insert(item.getBoundaries());
		}
	}

	public void removeObject(AbstractObject item)
	{
		item.getStatus().setHealth(0);
		if(item instanceof MovableObject) movableobjects.remove(item);
		if(item instanceof DisplayableObject) displayableobjects.remove(item);
		if(item.getOwner() != null && item.getOwner() instanceof Ship && item instanceof Bullet)
			((Ship)item.getOwner()).setbullet(null);
		if(item.getOwner() != null && item.getOwner() instanceof Ship && item instanceof Mine)
			((Ship)item.getOwner()).setmine(null);
		deinsert(item);
		remove(item.getBoundaries());
	}

	public void remove(ArrayList<Boundary> boundaries)
	{
		for(int x = 0; x < boundaries.size(); x++) remove(boundaries.get(x));
	}

	public void remove(Boundary boundary)
	{
		if(boundary.getDirection() == Direction.NORTH || boundary.getDirection() == Direction.SOUTH)
		{
			boundariesh.remove(boundary);
			return;
		}

		if(boundary.getDirection() == Direction.EAST || boundary.getDirection() == Direction.WEST)
		{
			boundariesv.remove(boundary);
			return;
		}

		Printer.err.println("ERROR 432: Could not remove Boundary from boundary list due to unknown direction #AForceEnv.remove(Boundary)");
	}


	public int getTeam()
	{
		return 0;
	}

	public void setTeam(int team)
	{
		// Environment is teamless, always returns zero
		Printer.wrn.println("WARNING 858: Unable to set team on teamless object");
		return;
	}


	public AbstractObject getOwner()
	{
		return null;
	}

	@Deprecated
	public void deinsert(AbstractObject item)
	{
		if(item instanceof MovableObject) movableobjects.remove(item);
		else if(item instanceof DisplayableObject) displayableobjects.remove(item);
		else if(item instanceof AbstractObject) abstractobjects.remove(item);
	}



	public void sort()
	{
		// We need to rewrite the sort method, it needs to completely sort
		// both the ver and hor lists of boundaries.  If an element does move
		// one space, the same element is likely to move many more spaces
		// this should be taken into account in the sorting method.
	}


	private void insert(Boundary boundary)
	{
		// vertical array addition.  The directions in the if statement may seem a little
		//   backwards, but the reason is beacause the boundaries which will be checked when
		//   a ship goes north or south will be facing east or west.  This is NOT a bug!
		if(boundary.getDirection() == Direction.EAST || boundary.getDirection() == Direction.WEST)
		{
			boolean changed = false;
			for(int x = 0; x < boundariesv.size(); x++)
			{
				// Boundaries are added from smallest to largest
				if((boundariesv.get(x)).getlocation().gety() > boundary.getlocation().gety())
				{
					boundariesv.add(x, boundary);
					changed = true;
					break;
				}
			}
			if(!changed) boundariesv.add(boundary); // adds item to the end if it hasn't been added
		}


		if(boundary.getDirection() == Direction.NORTH || boundary.getDirection() == Direction.SOUTH)
		{
			boolean changed = false;  //sorthing the horiz, reset changed for new array
			for(int x = 0; x < boundariesh.size(); x++)
			{
				// Boundaries are added from smallest to largest
				if((boundariesh.get(x)).getlocation().getx() > boundary.getlocation().getx())
				{
					boundariesh.add(x, boundary);
					changed = true;
					break;
				}
			}
			if(!changed) boundariesh.add(boundary); // adds item to the end if it hasn't been added
		}
	}


	private void insert(ArrayList<Boundary> boundarylist)
	{
		for(int x = 0; x < boundarylist.size(); x++)
		{
			insert(boundarylist.get(x));
		}

	}


	public ArrayList<Boundary> getBoundariesV(int x1, int y1, int x2, int y2)
	{
		ArrayList<Boundary> output = new ArrayList<Boundary>();
		for(int i = 0; i < boundariesh.size(); i++)
		{
			if(boundariesh.get(i).gety1() > y2) continue;
			if(boundariesh.get(i).gety2() < y1) continue;
			if(boundariesh.get(i).getx1() > x2) continue;
			if(boundariesh.get(i).getx2() < x1) continue;
			output.add(boundariesh.get(i));
		}
		return output;
	}

	public ArrayList<Boundary> getBoundariesH(int x1, int y1, int x2, int y2)
	{
		ArrayList<Boundary> output = new ArrayList<Boundary>();
		for(int i = 0; i < boundariesv.size(); i++)
		{
			Boundary boundary = boundariesv.get(i);
			if(boundary.gety1() > y2) continue;
			if(boundary.gety2() < y1) continue;
			if(boundary.getx1() > x2) continue;
			if(boundary.getx2() < x1) continue;
			output.add(boundariesv.get(i));
		}
		return output;
	}



	public boolean collisionCheck(MovableObject item)
	{
		return collisionCheck(item, item.getDirection());
	}


	//returns false unless it is hitting a block and the direction passed != item's direction!
	public boolean collisionCheck(MovableObject item, int direction)
	{
		ArrayList<Boundary> list = null;
	
		if(direction == Direction.NORTH || direction == Direction.SOUTH)
			list = getBoundariesH(item.relaventBoundary(direction).getx1(), item.relaventBoundary(direction).gety1()-1, item.relaventBoundary(direction).getx2(), item.relaventBoundary(direction).gety2()+1);
		if(direction == Direction.EAST || direction == Direction.WEST)
			list = getBoundariesV(item.relaventBoundary(direction).getx1()-1, item.relaventBoundary(direction).gety1(), item.relaventBoundary(direction).getx2()+1, item.relaventBoundary(direction).gety2());

		if(list == null)
		{
			Printer.err.println("List in AForceEnv.collisioncheck(args) is NULL!");
			Printer.err.println("Printing MovableObject (Direction passed:"+direction+"):");
			Printer.err.println(item);
		}

		for(int x = 0; x < list.size(); x++)
		{
			if(list.get(x).getOwner() instanceof Block && direction != item.getDirection())
				return true;
			else Status.collision(this, item, list.get(x).getOwner());
			if(item.getStatus().getHealth() == 0) return false;
		}

		return false;
	}


	public void click()
	{
		for(int x = 0; x < movableobjects.size(); x++)
		{
			MovableObject item = movableobjects.get(x);
			if(item.getStatus().getHealth() == 0)
				Printer.wrn.println("WARNING 398: A movable object with no health has been detected!");
			if(item.getStatus().getHealth() != 0) item.move();
		}
		if(numobjects(MovableObject.class) == 1)
		{
			AForce.pause();
			aforce.nextlevel();
		}
		Thread.yield();
		try{ Thread.sleep(1); } catch(Exception e){}
	}



	public ArrayList<DisplayableObject> getAllDisplayableObjects()
	{
		ArrayList<DisplayableObject> allobjects = new ArrayList<DisplayableObject>();
		allobjects.addAll(displayableobjects);
		allobjects.addAll(movableobjects);
		return allobjects;
	}


	public String toString(String[] args)
	{
		return "AForceEnv";
	}



	public static void main(String[] args)
	{
		Printer.noexecute();
	}

}
